 /*******************************************************************************
  * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.intro.impl.model.util;

 import java.util.Enumeration ;
 import java.util.Properties ;
 import java.util.Vector ;

 import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage;
 import org.eclipse.ui.internal.intro.impl.model.IntroExtensionContent;
 import org.eclipse.ui.internal.intro.impl.model.url.IntroURLParser;
 import org.eclipse.ui.internal.intro.impl.util.Log;
 import org.osgi.framework.Bundle;
 import org.w3c.dom.Document ;
 import org.w3c.dom.Element ;
 import org.w3c.dom.Node ;
 import org.w3c.dom.NodeList ;


 /**
  * Util class for model. Has methods for resolving model attributes, and methods
  * for manipulating XHTML DOM.
  */
 public class ModelUtil {

     private static String TAG_BODY = "body"; //$NON-NLS-1$
 private static String TAG_HEAD = "head"; //$NON-NLS-1$
 private static String TAG_BASE = "base"; //$NON-NLS-1$
 public static String TAG_DIV = "div"; //$NON-NLS-1$
 public static String TAG_HEAD_LINK = "link"; //$NON-NLS-1$
 private static String TAG_PARAM = "param"; //$NON-NLS-1$
 private static String ATT_SRC = "src"; //$NON-NLS-1$
 private static String ATT_HREF = "href"; //$NON-NLS-1$
 private static String ATT_CITE = "cite"; //$NON-NLS-1$
 private static String ATT_LONGDESC = "longdesc"; //$NON-NLS-1$
 private static String ATT_DATA = "data"; //$NON-NLS-1$
 private static String ATT_CODEBASE = "codebase"; //$NON-NLS-1$
 private static String ATT_VALUE = "value"; //$NON-NLS-1$
 private static String ATT_VALUE_TYPE = "valuetype"; //$NON-NLS-1$
 private static String ATT_REL = "rel"; //$NON-NLS-1$
 private static String ATT_TYPE = "type"; //$NON-NLS-1$



     /*
      * ********* Model util methods ************************************
      */

     /**
      * Checks to see if the passed string is a valid URL (has a protocol), if
      * yes, returns it as is. If no, treats it as a resource relative to the
      * declaring plugin. Return the plugin relative location, fully qualified.
      * Retruns null if the passed string itself is null.
      *
      * @param resource
      * @param pluginDesc
      * @return returns the URL as is if it had a protocol.
      */
     public static String resolveURL(String url, String pluginId) {
         Bundle bundle = null;
         if (pluginId != null)
             // if pluginId is not null, use it.
 bundle = Platform.getBundle(pluginId);
         return resolveURL("", url, bundle); //$NON-NLS-1$
 }



     /**
      * Checks to see if the passed string is a valid URL (has a protocol), if
      * yes, returns it as is. If no, treats it as a resource relative to the
      * declaring plugin. Return the plugin relative location, fully qualified.
      * Retruns null if the passed string itself is null.
      *
      * @param resource
      * @param pluginDesc
      * @return returns the URL as is if it had a protocol.
      */
     public static String resolveURL(String url, IConfigurationElement element) {
         Bundle bundle = BundleUtil.getBundleFromConfigurationElement(element);
         return resolveURL("", url, bundle); //$NON-NLS-1$
 }



     /**
      * @see resolveURL(String url, IConfigurationElement element)
      */
     public static String resolveURL(String base, String url, Bundle bundle) {
         // quick exit
 if (url == null)
             return null;
         IntroURLParser parser = new IntroURLParser(url);
         if (parser.hasProtocol())
             return url;
         // make plugin relative url. Only now we need the bundle.
 return BundleUtil.getResolvedResourceLocation(base, url, bundle);
     }

     /**
      * Ensures that a file:// URL exists for the bundle root. This will
      * cause jarred bundles to be extracted into a cache directory.
      */
     public static void ensureFileURLsExist(Bundle bundle, String contentFile) {
         try {
             FileLocator.toFileURL(bundle.getEntry("/")); //$NON-NLS-1$
 } catch (Exception e) {
             if (contentFile != null)
                 Log.error("Failed to extract Intro content folder for: " //$NON-NLS-1$
 + contentFile, e);
         }
     }


     /**
      * Returns the path to the parent folder containing the passed content xml
      * file. It is assumed that the path is a local url representing a content
      * file.
      */
     public static String getParentFolderToString(String contentFilePath) {
         IPath path = getParentFolderPath(contentFilePath);
         return path.toString();
     }


     /*
      *
      * ******** XHTML DOM util methods *********************************
      */

     /**
      * Returns the path to the parent folder containing the passed content xml
      * file. It is assumed that the path is a local url representing a content
      * file.
      */
     public static String getParentFolderOSString(String contentFilePath) {
         IPath path = getParentFolderPath(contentFilePath);
         return path.toOSString();
     }

     /**
      * Returns the parent folder of the given path.
      */
     public static IPath getParentFolderPath(String contentFilePath) {
         IPath path = new Path(contentFilePath);
         path = path.removeLastSegments(1).addTrailingSeparator();
         return path;
     }




     public static void insertBase(Document dom, String baseURL) {
         // there should only be one head and one base element dom.
 NodeList headList = dom.getElementsByTagName(TAG_HEAD);
         Element head = (Element) headList.item(0);
         NodeList baseList = head.getElementsByTagName(TAG_BASE);
         if (baseList.getLength() == 0) {
             // insert a base element, since one is not defined already.
 Element base = dom.createElement(TAG_BASE);
             base.setAttribute(ATT_HREF, baseURL);
             head.insertBefore(base, head.getFirstChild());
         }
     }


     public static Element getBase(Document dom) {
         // there should only be one head and one base element dom.
 NodeList headList = dom.getElementsByTagName(TAG_HEAD);
         Element head = (Element) headList.item(0);
         NodeList baseList = head.getElementsByTagName(TAG_BASE);
         if (baseList.getLength() == 0)
             // no base defined, signal failure.
 return null;

         return (Element) baseList.item(baseList.getLength() - 1);

     }


     // <link rel="stylesheet" HREF="shared.css" type="text/css" />
 public static void insertStyle(Document dom, String cssUrl) {
         // there should only be one head and one base element dom.
 NodeList headList = dom.getElementsByTagName(TAG_HEAD);
         Element head = null;
         // Element base = getBase(dom);
 NodeList styleList = null;
         // there can be more than one style. DO not add style if it exists.
 if (headList.getLength() >= 1) {
             head = (Element) headList.item(0);
             styleList = head.getElementsByTagName(TAG_HEAD_LINK);
             for (int i = 0; i < styleList.getLength(); i++) {
                 Element style = (Element) styleList.item(0);
                 String styleString = style.getAttribute(ATT_HREF);
                 if (styleString.equals(cssUrl))
                     return;
             }
         }

         // insert the style, since it is not defined.
 Element styleToAdd = dom.createElement(TAG_HEAD_LINK);
         styleToAdd.setAttribute(ATT_HREF, cssUrl);
         styleToAdd.setAttribute(ATT_REL, "stylesheet"); //$NON-NLS-1$
 styleToAdd.setAttribute(ATT_TYPE, "text/css"); //$NON-NLS-1$
 if (styleList != null && styleList.getLength() >= 1)
             styleList.item(0).getParentNode().insertBefore(styleToAdd,
                 styleList.item(0));
         else
             head.appendChild(styleToAdd);

     }

     /**
      * Returns a reference to the body of the DOM.
      *
      * @param dom
      * @return
      */
     public static Element getBodyElement(Document dom) {
         // there should only be one body element dom.
 NodeList bodyList = dom.getElementsByTagName(TAG_BODY);
         Element body = (Element) bodyList.item(0);
         return body;
     }



     public static Element createElement(Document dom, String elementName,
             Properties attributes) {

         // make sure to create element with any namespace uri to enable finding
 // it again using Dom.getElementsByTagNameNS()
 Element element = dom.createElementNS("", elementName); //$NON-NLS-1$
 if (attributes != null) {
             Enumeration e = attributes.keys();
             while (e.hasMoreElements()) {
                 String key = (String ) e.nextElement();
                 element.setAttribute(key, attributes.getProperty(key));
             }
         }
         return element;
     }

     public static Element createAndAppendChild(Element parentElement,
             String elementName, Properties attributes) {

         Element element = createElement(parentElement.getOwnerDocument(),
             elementName, attributes);
         parentElement.appendChild(element);
         return element;
     }



     /**
      * Returns an Element array of all first level descendant Elements with a
      * given tag name, in the order in which they are encountered in the DOM.
      * Unlike the JAXP apis, which returns preorder traversal of this Element
      * tree, this method filters out children deeper than first level child
      * nodes.
      */
     public static Element[] getElementsByTagName(Element parent, String tagName) {
         NodeList allChildElements = parent.getElementsByTagName(tagName);
         Vector vector = new Vector ();
         for (int i = 0; i < allChildElements.getLength(); i++) {
             // we know that the nodelist is of elements.
 Element aElement = (Element) allChildElements.item(i);
             if (aElement.getParentNode().equals(parent))
                 // first level child element. add it.
 vector.add(aElement);
         }
         Element[] filteredElements = new Element[vector.size()];
         vector.copyInto(filteredElements);
         return filteredElements;
     }

     /**
      * Same as getElementsByTagName(Element parent, String tagName) but the
      * parent element is assumed to be the root of the document.
      *
      * @see getElementsByTagName(Element parent, String tagName)
      */
     public static Element[] getElementsByTagName(Document dom, String tagName) {
         NodeList allChildElements = dom.getElementsByTagName(tagName);
         Vector vector = new Vector ();
         for (int i = 0; i < allChildElements.getLength(); i++) {
             // we know that the nodelist is of elements.
 Element aElement = (Element) allChildElements.item(i);
             if (aElement.getParentNode().equals(dom.getDocumentElement()))
                 // first level child element. add it. Cant use getParent
 // here.
 vector.add(aElement);
         }
         Element[] filteredElements = new Element[vector.size()];
         vector.copyInto(filteredElements);
         return filteredElements;
     }


     /*
      * Util method similar to DOM getElementById() method, but it works without
      * an id attribute being specified. Deep searches all children in this
      * container's DOM for the first child with the given id. The element
      * retrieved must have the passed local name. Note that in an XHTML file
      * (aka DOM) elements should have a unique id within the scope of a
      * document. We use local name because this allows for finding intro
      * anchors, includes and dynamic content element regardless of whether or
      * not an xmlns was used in the xml.
      */
     public static Element getElementById(Document dom, String id,
             String localElementName) {
         
         NodeList children = dom.getElementsByTagNameNS("*", localElementName); //$NON-NLS-1$
 for (int i = 0; i < children.getLength(); i++) {
             Element element = (Element) children.item(i);
             if (element.getAttribute("id").equals(id)) //$NON-NLS-1$
 return element;
         }
         // non found.
 return null;

     }

     public static Element getElementById(Document dom, String id) {
         return getElementById(dom, id, "*"); //$NON-NLS-1$
 }
     
     public static void updateResourceAttributes(Element element,
             AbstractIntroPage page) {
         updateResourceAttributes(element, page.getBase(), page.getBundle());
     }


     public static void updateResourceAttributes(Element element,
             IntroExtensionContent extensionContent) {
         updateResourceAttributes(element, extensionContent.getBase(),
             extensionContent.getBundle());
     }

     /**
      * Updates all the resource attributes of the passed element to point to a
      * local resolved url.
      *
      * @param element
      * @param extensionContent
      */
     private static void updateResourceAttributes(Element element, String base,
             Bundle bundle) {
         // doUpdateResourceAttributes(element, base, bundle);
 NodeList children = element.getElementsByTagName("*"); //$NON-NLS-1$
 for (int i = 0; i < children.getLength(); i++) {
             Element child = (Element) children.item(i);
             doUpdateResourceAttributes(child, base, bundle);
         }
     }

     private static void doUpdateResourceAttributes(Element element,
             String base, Bundle bundle) {
         qualifyAttribute(element, ATT_SRC, base, bundle);
         qualifyAttribute(element, ATT_HREF, base, bundle);
         qualifyAttribute(element, ATT_CITE, base, bundle);
         qualifyAttribute(element, ATT_LONGDESC, base, bundle);
         qualifyAttribute(element, ATT_CODEBASE, base, bundle);
         qualifyAttribute(element, ATT_DATA, base, bundle);
         qualifyValueAttribute(element, base, bundle);
     }

     private static void qualifyAttribute(Element element, String attributeName,
             String base, Bundle bundle) {
         if (element.hasAttribute(attributeName)) {
             String attributeValue = element.getAttribute(attributeName);
             if (new IntroURLParser(attributeValue).hasProtocol())
                 return;

             // resolve the resource against the nl mechanism.
 String attributePath = BundleUtil.getResolvedResourceLocation(base,
                 attributeValue, bundle);
             element.setAttribute(attributeName, attributePath);
         }
     }

     private static void qualifyValueAttribute(Element element, String base,
             Bundle bundle) {
         if (element.hasAttribute(ATT_VALUE)
                 && element.hasAttribute(ATT_VALUE_TYPE)
                 && element.getAttribute(ATT_VALUE_TYPE).equals("ref") //$NON-NLS-1$
 && element.getLocalName().equals(TAG_PARAM)) {
             String value = element.getAttribute(ATT_VALUE);
             if (new IntroURLParser(value).hasProtocol())
                 return;
             // resolve the resource against the nl mechanism.
 String attributePath = BundleUtil.getResolvedResourceLocation(base,
                 value, bundle);
             element.setAttribute(ATT_VALUE, attributePath);
         }
     }


     /**
      * Returns an array version of the passed NodeList. Used to work around DOM
      * design issues.
      */
     public static Node [] getArray(NodeList nodeList) {
         Node [] nodes = new Node [nodeList.getLength()];
         for (int i = 0; i < nodeList.getLength(); i++)
             nodes[i] = nodeList.item(i);
         return nodes;
     }


     /**
      * Remove all instances of the element from the DOM.
      *
      */
     public static void removeAllElements(Document dom, String elementLocalName) {
         // get all elements in DOM and remove them.
 NodeList elements = dom.getElementsByTagNameNS("*", //$NON-NLS-1$
 elementLocalName);
         // get the array version of the nodelist to work around DOM api design.
 Node [] elementsArray = ModelUtil.getArray(elements);
         for (int i = 0; i < elementsArray.length; i++) {
             Node element = elementsArray[i];
             element.getParentNode().removeChild(element);
         }

     }



 }

